package com.apanal.qlife.common.aop;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.Date;

import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.alibaba.fastjson.JSON;
import com.apanal.qlife.common.annotation.LogType;
import com.apanal.qlife.common.annotation.SystemLog;
import com.apanal.qlife.common.constants.Constants;
import com.apanal.qlife.common.exception.BusinessException;
import com.apanal.qlife.common.util.ExceptionUtil;
import com.apanal.qlife.common.util.IdWorkerFactory;
import com.apanal.qlife.common.util.IpUtil;
import com.apanal.qlife.common.util.PropertieUtil;
import com.apanal.qlife.sys.model.SysLog;
import com.apanal.qlife.sys.model.User;
import com.apanal.qlife.sys.service.SysLogService;
import com.apanal.qlife.sys.service.UserService;

/**
 * 系统操作日志切面类
 * 
 * @Order(1)是为了与数据库事务(order=0)保持相对独立, 不一起回滚或提交
 * 
 * @author shuliangxing
 * 
 * @date 2015-9-21上午11:11:36
 */
@Order(1)
@Aspect
@Component
public class SysLogAspect {
	private static final Logger logger = LoggerFactory
			.getLogger(SysLogAspect.class);

	@Autowired
	UserService userService;

	@Autowired
	SysLogService sysLogService;

	/**
	 * 日志分组id在request中的标识
	 */
	public static final String LOG_GROUP_ID = "LOG_GROUP_ID";

	// 操作日志记录总开关
	private static final String OPERATION_LOG = "OPERATION_LOG";

	// 操作日志类型开启配置,英文逗号分隔
	private static final String LOG_OP_TYPE = "LOG_OP_TYPE";

	// url存储长度
	private static final int LENGTH_URL = 200;

	// 目标方法名存储长度
	private static final int LENGTH_METHOD = 200;

	// 异常信息存储长度
	private static final int LENGTH_EX_MSG = 1000;

	// 异常堆栈信息存储长度
	private static final int LENGTH_EX_STACK = 5000;

	// 请求参数信息存储长度
	private static final int LENGTH_PARAM = 2000;

	/**
	 * 注解切点(合并的写法,但argName是'或者'的关系,不知道编写格式)
	 */
	// @Pointcut(value =
	// "( execution(* com.apanal.qlife.*.controller..*.*(..)) || execution(* com.apanal.qlife.*.service..*.*(..)) ) && ( @annotation(reqMapping) || @annotation(systemLog) )",
	// argNames = "reqMapping,systemLog")
	// public void logPointcut(RequestMapping reqMapping, SystemLog systemLog) {
	// }

	/**
	 * RequestMapping注解切点
	 */
	@Pointcut(value = "execution(* com.apanal.qlife.*.controller..*.*(..)) && @annotation(reqMapping) && !@annotation(com.apanal.qlife.common.annotation.SystemLog)", argNames = "reqMapping")
	public void reqMappingPointcut(RequestMapping reqMapping) {
	}

	/**
	 * SystemLog注解切点
	 */
	@Pointcut(value = "@annotation(systemLog)", argNames = "systemLog")
	public void systemLogPointcut(SystemLog systemLog) {
	}

	/**
	 * 含RequestMapping注解的环绕通知
	 * 
	 * @param pjp
	 * @return
	 * @throws Throwable
	 */
	@Around("reqMappingPointcut(reqMapping)")
	public Object doAroundReqMapping(ProceedingJoinPoint pjp,
			RequestMapping reqMapping) throws Throwable {
		return doLog(pjp, null);
	}

	/**
	 * 含SystemLog注解的环绕通知
	 * 
	 * @param pjp
	 * @return
	 * @throws Throwable
	 */
	@Around("systemLogPointcut(systemLog)")
	public Object doAroundSystemLog(ProceedingJoinPoint pjp, SystemLog systemLog)
			throws Throwable {
		return doLog(pjp, systemLog);
	}

	/**
	 * 记录日志
	 * 
	 * @param pjp
	 * @param systemLog
	 * @return
	 * @throws Throwable
	 */
	public Object doLog(ProceedingJoinPoint pjp, SystemLog systemLog)
			throws Throwable {
		// 日志记录总开关开启则记录,否则直接执行目标方法
		if (Boolean.valueOf(PropertieUtil.getPropertie(OPERATION_LOG))) {
			HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
					.getRequestAttributes()).getRequest();
			// 返回结果
			Object returning = null;
			SysLog log = new SysLog();
			// 日志对象
			beforeLog(log, request);
			try {
				// 执行目标对象方法
				returning = pjp.proceed();
			} catch (Exception e) {
				exceptionLog(log, e);
				throw e;
			} finally {
				// 记录日志的情况:结果非已知业务异常、结果正常且注解非空且配置对应类型开启、结果业务错误且注解非空且配置对应类型开启且注解对应开关开启
				if (log.getLogResult() == 0
						|| (systemLog != null
								&& isTypeToLog(systemLog.type().value()) && (log
								.getLogResult() == 1 || systemLog.busiEx()))) {
					String methodName = pjp.getTarget().getClass().getName()
							+ "." + pjp.getSignature().getName() + "()";

					// 模块
					String module = Constants.UNDEFINED;
					// 操作
					String operation = Constants.UNDEFINED;
					// 类型 -1:未知(0:其它 1:新增 2:删除 3修改 4:查询 5:登录 6:发送短信 7:发送邮件)
					LogType type = LogType.UNDEFINED;
					if (systemLog != null) {
						module = systemLog.module();
						operation = systemLog.operation();
						type = systemLog.type();
					}
					afterLog(log, request, methodName, module, operation, type);
				}
			}
			return returning;
		} else {
			return pjp.proceed();
		}
	}

	/**
	 * 方法执行前的日志信息设置
	 * 
	 * @param log
	 * @param request
	 */
	public void beforeLog(SysLog log, HttpServletRequest request) {
		// 目标方法结果类型, 0:异常 1:正常 2:业务错误(业务异常)
		log.setLogResult(1);
		// 日志分组,方便查看多个日志嵌套的情况
		Long logGroup = (Long) request.getAttribute(LOG_GROUP_ID);
		if (logGroup == null) {
			logGroup = IdWorkerFactory.nextId();
			request.setAttribute(LOG_GROUP_ID, logGroup);
		}
		log.setLogGroup(logGroup);
		// 开始/日志时间
		log.setLogTime(new Date());
	}

	/**
	 * 方法执行异常的日志信息设置
	 * 
	 * @param log
	 * @param e
	 */
	public void exceptionLog(SysLog log, Exception e) {
		log.setLogResult((e instanceof BusinessException) ? 2 : 0);
		// 异常信息
		log.setLogException(getSubstring(
				e.getClass().getName() + ": " + e.getMessage(), LENGTH_EX_MSG));
		// 堆栈信息,过长截取
		log.setLogStackTrace(getSubstring(ExceptionUtil.getStackTrace(e),
				LENGTH_EX_STACK));
	}

	/**
	 * 方法执行后的日志信息设置
	 * 
	 * @param log
	 *            SysLog日志对象
	 * @param request
	 *            请求
	 * @param methodName
	 *            方法名
	 * @param module
	 *            模块名
	 * @param operation
	 *            操作
	 * @param type
	 *            类型
	 */
	public void afterLog(SysLog log, HttpServletRequest request,
			String methodName, String module, String operation, LogType type) {
		// 结束时间
		Long endTime = System.currentTimeMillis();
		setRequestAttr(request, log);
		// 执行时长
		log.setLogDuration(endTime - log.getLogTime().getTime());
		// 用户id,0位匿名用户
		Long useId = 0l;
		String username = (String) SecurityUtils.getSubject().getPrincipal();
		if (username != null) {
			// 当前用户
			User user = userService.findByUsername(username);
			useId = user.getUseId();
		}
		log.setUseId(useId);
		// 请求方法
		log.setLogMethod(getSubstring(methodName, LENGTH_METHOD));

		// 模块
		log.setLogModule(module);
		// 操作
		log.setLogOperation(operation);
		// 类型
		log.setLogType(type.value());

		// 插入日志
		sysLogService.insert(log);
	}

	/**
	 * 设置请求参数
	 * 
	 * @param request
	 * @param log
	 */
	private void setRequestAttr(HttpServletRequest request, SysLog log) {
		// 请求方式
		log.setLogReqMethod(request.getMethod());
		// url
		log.setLogUrl(getSubstring(request.getRequestURL().toString(),
				LENGTH_URL));
		if (request.getParameterMap() != null
				&& request.getParameterMap().size() > 0) {
			// 请求参数
			String param = null;
			if (request.getMethod().equalsIgnoreCase("get")) {
				param = request.getQueryString();
				try {
					param = URLDecoder.decode(param, "utf-8");
				} catch (UnsupportedEncodingException e) {
					logger.error("url参数解码错误,queryString=" + param, e);
				}
			} else {
				param = JSON.toJSONString(request.getParameterMap());
			}
			log.setLogParam(getSubstring(param, LENGTH_PARAM));
		}
		// ip
		log.setLogIp(IpUtil.getIpAddr(request));
	}

	/**
	 * 根据长度截取字符串
	 * 
	 * @param str
	 * @param length
	 */
	private String getSubstring(String str, int length) {
		if (str != null && str.length() > length) {
			str = str.substring(0, length - 20) + " 省略...";
		}
		return str;
	}

	/**
	 * 操作类型日志是否记录
	 * 
	 * @param logType
	 * @return
	 */
	private boolean isTypeToLog(int logType) {
		// 操作日志类型开启配置,英文逗号分隔
		String opType = PropertieUtil.getPropertie(LOG_OP_TYPE);
		if (opType == null || "".equals(opType.trim())) {
			return false;
		} else {
			String[] typeStr = opType.split(",");
			for (int i = 0; i < typeStr.length; i++) {
				if (Integer.valueOf(typeStr[i].trim()) == logType) {
					return true;
				}
			}
			return false;
		}
	}

}